<?php


namespace App\Command\ExportFile;

use App\Command\ResumeParsing\Service\ParsingFunction;
use App\Service\DbIndex;
use App\Service\ExternalAPI\VApi;
use App\Service\FileHandler;
use App\Web\Model\CaseModel;
use App\Web\Model\CaseResume as CaseResumeModel;
use App\Web\Model\Project as ProjectModel;
use App\Web\Model\ResumeRemark;
use EchoPHP\Facades\File;
use EchoPHP\Facades\Log;
use App\Web\Model\Resume as ResumeModel;
use EchoPHP\Facades\Db as Db;
use GuzzleHttp\Exception\GuzzleException;
use PhpOffice\PhpWord\TemplateProcessor;
use App\Web\Model\User;

class DocxExportHelper {
    public $templates = [];
    public $exportDir;
    protected $tmpDir;
    public $pos;
    protected $configPath;
    /**
     * @var VApi
     */
    private $vApi;
    /**
     * @var ParsingFunction
     */
    private $func;
    /**
     * @var ResumeRemark
     */
    private $resumeRemark;
    /**
     * @var ResumeModel
     */
    private $model;
    /**
     * @var ExcelExportHelper
     */
    private $excelExportHelper;
    /**
     * @var CaseResumeModel
     */
    private $caseResumeModel;

    public function __construct(string $configPath, array &$pos) {
        $this->exportDir = config('app.file_export.directory');
        $this->tmpDir = $this->exportDir . '/tmp/' . date('Y_m') . '/';
        if (!is_dir($this->tmpDir)) {
            if (file_exists($this->tmpDir)) @unlink($this->tmpDir);
            File::makeDirectory($this->tmpDir);
        }
        $this->templates[1] = ['path' => ROOT_PATH . '/app/Command/ExportFile/resource/mb.docx', 'hasAvatar' => true, 'avatarSize' => [110, 145]];    //广州-简历推荐-重点岗位推荐专用（前程无忧人才推荐报告）
        $this->templates[2] = ['path' => ROOT_PATH . '/app/Command/ExportFile/resource/mb2.docx', 'hasAvatar' => false];   //深圳-定制化-候选人推荐报告
        $this->templates[3] = ['path' => ROOT_PATH . '/app/Command/ExportFile/resource/mb3.docx', 'hasAvatar' => false];   //北京-前程无忧推荐报告
        $this->templates[4] = ['path' => ROOT_PATH . '/app/Command/ExportFile/resource/mb4.docx', 'hasAvatar' => false];   //上海-简历推荐报告
        $this->templates[5] = ['path' => ROOT_PATH . '/app/Command/ExportFile/resource/mb5.docx', 'hasAvatar' => true, 'avatarSize' => [85, 103]];   //前程无忧简历模板
        $this->templates[6] = ['isExcel' => true, 'excelTemplateId' => 6, 'withDetailSheet' => true];   //通用-Excel
        $this->templates[7] = ['isExcel' => true, 'excelTemplateId' => 6, 'withDetailSheet' => false];   //通用-Excel
        $this->model = new ResumeModel();
        $this->resumeRemark = new ResumeRemark();
        $this->configPath = $configPath;
        $this->pos =& $pos;
        $this->func = new ParsingFunction();
        $this->vApi = new VApi();
        $this->excelExportHelper = new ExcelExportHelper();
        $this->caseResumeModel = new CaseResumeModel();
    }

    public function export($id, $templateid, $params, $creator) {
        if ($id) $this->openTask($id);
        $resumeIds = $params['resumeids'] ?? '';
        $caseResumeIds = $params['caseresumeids'] ?? '';
        $projectId = $params['projectid'] ?? '';
        if (empty($this->templates[$templateid]['isExcel'])) {    //导出不为Excel
            $caseInfo = [];
            if (!empty($caseResumeIds) && is_string($caseResumeIds)) {
                $idArr = explode(",", $caseResumeIds);

                if (empty($projectId)) {
                    $this->finishTask($id, 0);
                    return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
                }

                //获取客户id以获取分库信息
                $sql = 'select subid from Project where projectid=? ';
                $result = Db::selectOne($sql, [$projectId]);

                if (!empty($result['subid'])) {
                    $dbIndex = DbIndex::getDbIndex($result['subid']);
                    $resultList = $this->caseResumeModel->getExportListByIds($idArr, $dbIndex['dbid'], $dbIndex['tableid'], 'id,resumeid,caseid');
                }
                if (empty($resultList) || !is_array($resultList) || count($resultList) < 1) {
                    $this->finishTask($id, 0);
                    return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
                }

                $resumeIds = '';
                $caseIds = [];
                $tmpCaseResume = [];
                $caseResumeDict = [];
                foreach ($resultList as $item) {
                    if (!empty($item['caseid'])) {
                        if (!in_array($item['caseid'], $caseIds)) {
                            $caseIds[] = $item['caseid'];
                        }
                        $tmpCaseResume[$item['resumeid']] = $item['caseid'];
                    }
                    $resumeIds .= $item['resumeid'] . ',';
                    if (!empty($item['caseid'])) {
                        $caseResumeDict[$item['resumeid'] . '_' . $item['caseid']] = ['caseid' => $item['caseid'], 'resumeid' => $item['resumeid']];
                    }
                }
                if (!empty($caseIds)) {
                    $caseModel = new CaseModel();
                    $caseList = $caseModel->getCaseList($caseIds, 'caseid,casename');
                    if (is_array($caseList) && count($caseList) > 0) {
                        foreach ($caseList as $item) {
                            $caseInfo[$item['caseid']] = $item;
                        }
                    }
                }
                $resumeIds = trim($resumeIds, ',');
            }

            if (empty($resumeIds) || !is_string($resumeIds)) {
                $this->finishTask($id, 0);
                return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
            }

            if (is_string($resumeIds)) $resumeIds = explode(',', $resumeIds);
            $this->model->arrValueToKey($resumeIds);
            $resumeInfoArr = $this->model->getResumeInfo($resumeIds, $creator);
            $this->model->hideFields($resumeInfoArr, $params);

            if (!empty($caseResumeDict)) {
                $remarkInfo = $this->resumeRemark->getLatest(array_keys($caseResumeDict), $creator);
                foreach ($remarkInfo as $remark) {
                    if (!empty($resumeInfoArr[$remark['resumeid']]) && is_array($resumeInfoArr[$remark['resumeid']])) {
                        $caseResumeDict[$remark['resumeid'] . '_' . $remark['caseid']] += $resumeInfoArr[$remark['resumeid']];
                        $caseResumeDict[$remark['resumeid'] . '_' . $remark['caseid']]['review'] = $remark['content'];
                    }
                }
                foreach ($caseResumeDict as &$item) {    //没有评语的case简历补齐
                    if (count($item) < 5 && !empty($resumeInfoArr[$item['resumeid']]) && is_array($resumeInfoArr[$item['resumeid']])) $item += $resumeInfoArr[$item['resumeid']];
                }
                $resumeInfoArr =& $caseResumeDict;
                //$this->caseResumeModel->getContactContent($resumeInfoArr);
            }

            $extraParams = ['caseInfo' => $caseInfo];
        } elseif (!empty($caseResumeIds)) {    //通过人才管理导出Excel
            $caseResumeList = array_filter(explode(',', $caseResumeIds));

            if (empty($projectId)) {
                $this->finishTask($id, 0);
                return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
            }

            //获取客户id以获取分库信息
            $sql = 'select subid from Project where projectid=? ';
            $result = Db::selectOne($sql, [$projectId]);
            if (empty($result['subid'])) {
                $this->finishTask($id, 0);
                return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
            }

            $totalCount = count($caseResumeList);

            $this->caseResumeModel->arrValueToKey($caseResumeList);

            $resumeList = $this->caseResumeModel->getCaseResumeList($creator, $result['subid'], $projectId, ['projectid' => $projectId, 'caseresumeid' => $caseResumeIds, 'isexport' => true]);

            $caseFlowList = config('app.global_caseflow');
            if (is_file($caseFlowList)) {
                $caseFlow = include $caseFlowList;
            } else {
                $caseFlow = [];
            }

            $projectModel = new ProjectModel();
            $projectIds = array_column($resumeList, 'projectid');
            $projectList = $projectModel->getProjectListById($projectIds);

            $caseStatusName = ['01' => '进行中', '02' => '暂停', '03' => '已结束'];
            foreach ($resumeList as $item) {
                $item['flowatinfo'] = '';
                if (!empty($item['flowat']) && !empty($tmpFlow = @$caseFlow[$item['flowat']]) && !empty($tmpFlow['name'])) {
                    $item['flowatinfo'] = $tmpFlow['name'];
                    if (!empty($item['flowatstatus']) && !empty($tmpFlow['child']) && !empty($tmpStatus = $tmpFlow['child'][$item['flowatstatus']]) && !empty($tmpStatus['name'])) {
                        $item['flowatinfo'] .= ' - ' . $tmpStatus['name'];
                    }
                }

                $item['casestatuscode'] = $item['casestatus'];
                $item['casestatus'] = isset($caseStatusName[$item['casestatuscode']]) ? $caseStatusName[$item['casestatuscode']] : '';
                $item['projectname'] = isset($projectList[$item['projectid']]) ? $projectList[$item['projectid']]['projectname'] : '';

                $caseResumeList[$item['id']] = $item;
            }
            $resumeInfoArr =& $caseResumeList;
            $this->caseResumeModel->getContactContent($resumeInfoArr);
            $extraParams = ['taskId' => $id];
        } elseif (!empty($resumeIds)) { //通过已下载简历导出Excel
            $resumeIdList = array_filter(explode(',', $resumeIds));
            $totalCount = count($resumeIdList);
            $resumeInfoArr = $this->caseResumeModel->getResumeList($resumeIdList, $creator);
            $extraParams = ['taskId' => $id];
        } else {
            $this->finishTask($id, 0);
            return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
        }
        $callback = null;
        if ($id) {
            $callback = function ($resumeCount) use ($id) {
                $this->updateProgress($id, $resumeCount);
            };
        }

        $extraParams['projectid'] = $projectId;
        $result = $this->exportToFile($templateid, $creator, $resumeInfoArr, $extraParams, $params, $totalCount ?? 0, $callback);
        if ($id) $this->finishTask($id, $result['resumecount'], $result['originalname'], $result['relativepathname']);
        return $result;
    }

    public function exportToFile($templateid, $creator, $arr, $extraParams, $exportFilter, $totalCount, callable $progressCallback = null) {
        if (empty($arr) || !is_array($arr)) return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
        if (isset($arr['resumeid'])) $arr = [$arr['resumeid'] => $arr];
        $result = [];
        try {
            if (!empty($this->templates[$templateid]['isExcel'])) {    //导出为Excel
                $sql = 'select id,fieldname,showname,unit,type,arguments,issys from ContactField where projectid = ? and status = ?';
                $var = [$extraParams['projectid'], '01'];
                $projectContact = Db::select($sql, $var);
                $exportResult = $this->excelExportHelper->export($arr, $this->templates[$templateid]['excelTemplateId'], $totalCount, true, $this->templates[$templateid]['withDetailSheet'], $creator, $exportFilter, $projectContact);
                $resumeCount = $exportResult['successCount'];
                if ($resumeCount == 0) return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
                $taskId = $extraParams['taskId'];

                $fileName = $this->tmpDir . '简历索引' . ($this->templates[$templateid]['withDetailSheet'] ? '（含详情）' : '') . '_' . $taskId . '_' . date('Y-m-d') . $exportResult['ext'];
                $exportResult['writer']->save($fileName);
                $result[] = $fileName;
                if (is_callable($progressCallback)) $progressCallback($resumeCount);
            } else {
                $caseInfo = $extraParams['caseInfo'];
                //获取RPO顾问姓名
                $userModel = new User();
                $userInfo = $userModel->getInfo($creator);

                $rpoName = '';
                $rpoPhone = '';
                if (is_array($userInfo) && isset($userInfo['cname'])) {
                    $rpoName = $userInfo['cname'];
                    $rpoPhone = $userInfo['mobilephone'];
                }

                foreach ($arr as $key => $resumeInfo) {
                    $resumeInfo['rponame'] = $rpoName;
                    $resumeInfo['rpophone'] = $rpoPhone;
                    $resumeInfo['casename'] = isset($resumeInfo['caseid']) && isset($caseInfo[$resumeInfo['caseid']]) ? $caseInfo[$resumeInfo['caseid']]['casename'] : '';
                    $fileName = $this->exportToSingleFile($templateid, $resumeInfo);
                    if (!empty($fileName) && is_string($fileName)) {
                        $result[] = $fileName;
                        if (is_callable($progressCallback)) $progressCallback(count($result));
                    }
                }
                $resumeCount = count($result);
            }
            if (count($result) > 0) {
                $resultFileName = $result[0];
                if (count($result) > 1) {
                    $zipName = $resultFileName . '等' . count($result) . '份简历.zip';
                    $zip = new \ZipArchive();
                    if (@$zip->open($zipName, \ZipArchive::CREATE) === TRUE) {
                        foreach ($result as $fileName) {
                            @$zip->addFile($fileName, basename($fileName));
                        }
                        @$zip->close();
                    }
                    $resultFileName = $zipName;
                }

                $fileObj = new FileHandler($resultFileName);
                $moveResult = $fileObj->move(true, $this->exportDir);
                $moveResult['resumecount'] = $resumeCount;
                return $moveResult;
            }
        } catch (\Exception $e) {
            Log::error(e_dump($e), ['taskids' => array_keys($arr)], 'cmd');
            if (!empty($resultFileName)) @unlink($resultFileName);
        } finally {
            foreach ($result as $fileName) {
                @unlink($fileName);
            }
        }
        return ['resumecount' => 0, 'originalname' => '', 'relativepathname' => ''];
    }

    protected function exportToSingleFile($templateid, $resumeInfo) {
        if (!is_array($resumeInfo)) return false;
        if (!isset($this->templates[$templateid]) || empty($this->templates[$templateid]['path'])) $templateid = 1;
        $template = $this->templates[$templateid];

        try {
            $tProcessor = new TemplateProcessor($template['path']);

            $resumeInfo['id'] = $resumeInfo['resumeid'];
            //$resumeInfo['birthday'] = !$this->isEmptyDate($resumeInfo['birthday']) ? date('Y年m月', strtotime($resumeInfo['birthday'])) : ''; //获取信息时已做转换
            $resumeInfo['company'] = count($resumeInfo['workinfo']) ? $resumeInfo['workinfo'][0]['company'] : '';
            $resumeInfo['today'] = date('Y年m月d日');
            $resumeInfo['language'] = '';
            if ($resumeInfo['platform'] == 'jingying' || ($resumeInfo['platform'] == '51job' && $resumeInfo['source'] == 9)) {
                $resumeInfo['selfintro'] = preg_replace('/\s*\|\s*/u', ' | ', $this->func->formatTxt(html_entity_decode(html_entity_decode(strip_tags($resumeInfo['profile'])))));
            }
            if (!empty($resumeInfo['currentsalaryinfo'])) {
                if (!empty($resumeInfo['currentsalaryinfo']['basesalary'])) $resumeInfo['basesalary'] = $resumeInfo['currentsalaryinfo']['basesalary'];
                if (!empty($resumeInfo['currentsalaryinfo']['bonus'])) $resumeInfo['bonus'] = $resumeInfo['currentsalaryinfo']['bonus'];
                if (!empty($resumeInfo['currentsalaryinfo']['total'])) $resumeInfo['annualsalary'] = $resumeInfo['currentsalaryinfo']['total'];
            }
            $this->onBeforeEdit($resumeInfo, $templateid);

            $cname = $resumeInfo['cname'];
            $this->formatOutput($resumeInfo);
            $basicFields = ['id', 'today', 'cname', 'casename', 'age', 'gender', 'area', 'birthday', 'married', 'phone', 'email', 'company', 'position', 'salary', 'expectsalary',
                'language', 'selfintro', 'awardinfo', 'otherinfo', 'review', 'degree', 'rponame', 'rpophone', 'current_situation', 'entrytime', 'bonus', 'basesalary', 'annualsalary',
                'age_birthday', 'workyearnum', 'updatedateshow', 'recentworktimespan'];
            foreach ($basicFields as $field) {
                $tProcessor->setValue($field, $resumeInfo[$field] ?? '');
            }

            if (isset($template['hasAvatar']) && $template['hasAvatar'] !== false) {
                $tmpAvatar = $this->getAvatar($resumeInfo);
                if (!empty($tmpAvatar['path'])) {
                    $avatarSize = !empty($template['avatarSize']) && is_array($template['avatarSize']) && count($template['avatarSize']) == 2 ? $template['avatarSize'] : [110, 145];
                    $tProcessor->setImg('img', ['src' => $tmpAvatar['path'], 'size' => $avatarSize]);
                } else {
                    $tProcessor->setValue('img', '');
                }
            }
            //$tProcessor->cloneTableRow('proj', ['time' => ['t1', 't2', 't3', 't4'], 'project' => ['p1', 'p2', 'p3', 'p4'], 'company' => ['c1', 'c2', 'c3', 'c4'], 'duty' => ['d1', 'd2', 'd3', 'd4'], 'content' => ['c1<w:br/>c2', 'c2', 'c3', 'c4']]);

            foreach ($resumeInfo['workinfo'] as &$item) {
                $item['content1'] = '';
                $item['content2'] = $item['content'];
            }

            $applyTable = function ($fieldName, $tableName, $hasTime = false) use (&$resumeInfo, &$tProcessor) {
                if (!empty($resumeInfo[$fieldName]) && is_array($resumeInfo[$fieldName])) {
                    if ($hasTime) $tProcessor->cloneTableRow($tableName, $this->array_flip_xy($this->formatTimespanData($resumeInfo[$fieldName])));
                    else $tProcessor->cloneTableRow($tableName, $this->array_flip_xy($resumeInfo[$fieldName]));
                }
            };

            $applyTable('workinfo', 'work', true);
            $applyTable('projectinfo', 'proj', true);
            $applyTable('educationinfo', 'edu', true);
            $applyTable('certification', 'cert', true);
            $applyTable('train', 'train', true);
            $applyTable('schoolaward', 'award', true);
            $applyTable('schooljob', 'schooljob', true);
            $applyTable('others', 'others', true);

            $applyTable('recentwork', 'recentwork');
            $applyTable('topedu', 'topedu');
            $applyTable('personal', 'personal');
            $applyTable('expect', 'expect');
            $applyTable('skill', 'skill');

            $this->onAfterEdit($tProcessor, $templateid, $resumeInfo);

            $cname = mb_substr(utf8_str_replace(['/', '\\', '?', '%', '*', ':', '|', '"', '<', '>'], ['／', '＼', '？', '％', '＊', '：', '｜', '＂', '＜', '＞'], $cname), 0, 100);
            $fileName = $this->tmpDir . $resumeInfo['resumeid'] . (empty($cname) ? '' : '_' . $cname) . (empty($resumeInfo['caseid']) ? '' : '_' . $resumeInfo['caseid']) . '.docx';
            $tProcessor->saveAs($fileName);
            return $fileName;
        } catch (\Exception $e) {
            Log::error(e_dump($e), ['resumeid' => $resumeInfo['resumeid']], 'cmd');
            if (!empty($fileName)) @unlink($fileName);
            return false;
        } finally {
            if (!empty($tmpAvatar) && $tmpAvatar['delFile']) {
                @unlink($tmpAvatar['path']);
            }
        }
    }

    protected function onBeforeEdit(array &$resume, int $templateId) {
        static $contactTypeMap = [
            "00" => "家庭电话：",
            "01" => "公司电话：",
            "02" => "微　信：",
            "03" => "QQ　号：",
        ];
        if (!$this->isEmptyDate($resume['birthdayoriginal'] ?? '')) {
            $timestamp = strtotime($resume['birthdayoriginal']);
            if ($resume['platform'] == '51job') {
                $birthday = date('（Y年m月d日）', $timestamp);
            } else {
                if (date('d', $timestamp) > 1) $birthday = date('（Y年m月d日）', $timestamp);
                elseif (date('m', $timestamp) > 1) $birthday = date('（Y年m月）', $timestamp);
            }
        }
        if (empty($birthday)) $birthday = empty($resume['birthday']) ? '' : '（' . $resume['birthday'] . '）';
        $resume['age_birthday'] = $resume['age'] . '岁' . $birthday;
        $recentWork = [];
        $recentWork['职　位：'] = $resume['recentduty'] ?? '';
        $recentWork['公　司：'] = $resume['recentcompany'] ?? '';
        $recentWork['行　业：'] = $resume['recentworkindustry'] ?? '';
        $resume['recentwork'] = $this->generateKeyValuePairs($recentWork);
        $resume['recentworktimespan'] = !empty($resume['recentworktimespan']) ? ' (' . $resume['recentworktimespan'] . ')' : '';
        $topEdu = [];
        $topEdu['专　业：'] = $resume['major'] ?? '';
        $topEdu['学　校：'] = $resume['school'] ?? '';
        $topEdu['学历/学位：'] = $resume['degree'] ?? '';
        $resume['topedu'] = $this->generateKeyValuePairs($topEdu);
        $personalInfo = [];
        if (!empty($resume['contacttype']) && !empty($resume['othercontacts'])) $personalInfo[$contactTypeMap[$resume['contacttype_code']]] = $resume['othercontacts'];
        if (!empty($resume['hukou'])) $personalInfo['户口/国籍：'] = $resume['hukou'];
        if (!empty($resume['married'])) $personalInfo['婚姻状况：'] = $resume['married'];
        if (!empty($resume['politic'])) $personalInfo['政治面貌：'] = $resume['politic'];
        if (!empty($resume['height'])) $personalInfo['身　高：'] = $resume['height'] . 'cm';
        if (!empty($resume['address'])) {
            $personalInfo['家庭住址：'] = $resume['address'];
            if (!empty($resume['zipcode'])) $personalInfo['家庭住址：'] .= ' (邮编：' . $resume['zipcode'] . '）';
        }
        $resume['personal'] = $this->generateKeyValuePairs($personalInfo, 2);
        $expect = [];
        if (!empty($resume['expectsalary'])) $expect['期望薪资：'] = $resume['expectsalary'];
        if (!empty($resume['expectarea'])) $expect['地　点：'] = $resume['expectarea'];
        if (!empty($resume['position'])) $expect['职　能：'] = $resume['position'];
        if (!empty($resume['workindustry'])) $expect['行　业：'] = $resume['workindustry'];
        if (!empty($resume['entrytime'])) $expect['到岗时间：'] = $resume['entrytime'];
        if (!empty($resume['seektype'])) $expect['工作类型：'] = $resume['seektype'];
        $resume['expect'] = $this->generateKeyValuePairs($expect, 2);
        if (is_array($resume['workinfo']) && count($resume['workinfo']) > 0) {
            foreach ($resume['workinfo'] as &$item) {
                $item['overseas'] = empty($item['isoverseas']) ? '' : '海外经历';
                if (!empty($item['department'])) $item['duty'] = $item['rawduty'];
                else $item['department'] = '';
                if (empty($item['duty']) && !empty($item['workfunc'])) $item['duty'] = $item['workfunc'];
                $item['timespan'] = empty($item['time']['timespan']) ? '' : ' (' . $item['time']['timespan'] . ')';

                $industryArr = [];
                if (!empty($item['workindustry'])) $industryArr[] = $item['workindustry'];
                if (!empty($item['companysize'])) $industryArr[] = $item['companysize'];
                if (!empty($item['companytype'])) $industryArr[] = $item['companytype'];
                if (!empty($item['worktype'])) $industryArr[] = $item['worktype'];
                if (!empty($item['salary'])) $industryArr[] = '薪资：' . $item['salary'];
                $item['workindustry'] = count($industryArr) ? "\r\n" . implode(' | ', $industryArr) : '';

                $extraInfoArr = [];
                if (!empty($item['reportperson'])) $extraInfoArr[] = '下属人数：' . $item['reportperson'];
                if (!empty($item['reportboss'])) $extraInfoArr[] = '汇报对象：' . $item['reportboss'];
                if (!empty($item['leavereason'])) $extraInfoArr[] = '离职原因：' . $item['leavereason'];
                $item['extrainfo'] = count($extraInfoArr) ? implode(' | ', $extraInfoArr) : '';

                if (!isset($item['score'])) $item['score'] = '';
            }
        }
        if (is_array($resume['projectinfo']) && count($resume['projectinfo']) > 0) {
            foreach ($resume['projectinfo'] as &$item) {
                if (!isset($item['function'])) $item['function'] = '';
            }
        }
        if (is_array($resume['educationinfo']) && count($resume['educationinfo']) > 0) {
            foreach ($resume['educationinfo'] as &$item) {
                $item['overseas'] = empty($item['isoverseas']) ? '' : '海外经历';
            }
        }
        $skill = [];
        if (!empty($resume['itskill']) && is_array($resume['itskill'])) {
            foreach ($resume['itskill'] as &$item) {
                $skill[$item['skilltext']] = $item['ability'];
            }
        }
        $resume['skill'] = $this->generateKeyValuePairs($skill, 2);

        if (is_array($resume['schoolaward']) && count($resume['schoolaward']) > 0) {
            foreach ($resume['schoolaward'] as &$item) {
                if (!empty(trim($item['cbonusclass']))) $item['cbonusclass'] = ' （' . $item['cbonusclass'] . '）';
            }
        }

        if (is_array($resume['certification']) && count($resume['certification']) > 0) {
            foreach ($resume['certification'] as &$item) {
                if (!empty(trim($item['score']))) $item['score'] = ' （' . $item['score'] . '）';
            }
        }
//            foreach ($resume as $k => $v) { //清空数据，测试用
//                if ($k != 'resumeid') {
//                    if (is_array($v)) $resume[$k] = [];
//                    else $resume[$k] = '';
//                }
//            }
        if (!empty($resume['area'])) $resume['area'] = '现居住' . $resume['area'];

        foreach (['current_situation', 'phone', 'email', 'gender', 'age_birthday', 'area', 'workyearnum'] as $fieldName) {
            if (!isset($resume[$fieldName])) $resume[$fieldName] = '';//'${undefined}';
            if (empty($resume[$fieldName])) $resume[$fieldName] = '';
        }

        if (!isset($resume['workinfo']) || !is_array($resume['workinfo']) || !count($resume['workinfo']))
            $resume['workinfo'] = [['time' => '', 'duty' => '', 'company' => '', 'workindustry' => '', 'timespan' => '',
                'content' => '', 'extrainfo' => '', 'score' => '', 'department' => '', 'overseas' => '']];
        if (!isset($resume['projectinfo']) || !is_array($resume['projectinfo']) || !count($resume['projectinfo']))
            $resume['projectinfo'] = [['time' => '', 'project' => '', 'company' => '', 'duty' => '',
                'content' => '', 'function' => '']];
        if (!isset($resume['educationinfo']) || !is_array($resume['educationinfo']) || !count($resume['educationinfo']))
            $resume['educationinfo'] = [['time' => '', 'school' => '', 'major' => '', 'degree' => '',
                'content' => '', 'overseas' => '']];
        if (!isset($resume['certification']) || !is_array($resume['certification']) || !count($resume['certification']))
            $resume['certification'] = [['time' => '', 'certname' => '', 'score' => '']];
        if (!isset($resume['train']) || !is_array($resume['train']) || !count($resume['train']))
            $resume['train'] = [['time' => '', 'ctrainname' => '', 'ctrainplace' => '', 'ctrainlesson' => '', 'cdescribe' => '']];
    }


    private function onAfterEdit(TemplateProcessor $tProcessor, int $templateId, array &$resume) {
        if ($templateId == 5) {
            $tProcessor->editDom(function (\DOMDocument $dom) use (&$resume) {
                $xPath = new \DOMXPath($dom);
                $removeByXpath = function ($expression) use ($xPath) {
                    $result = $xPath->query($expression);
                    foreach ($result as $node) {
                        $node->parentNode->removeChild($node);
                    }
                };

                $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[.=\'${recentwork.key}${recentwork.value}\'][1]');
                $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl[.=\'最近工作\'][1]');
                $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl[.=\'最高学历/学位${topedu.key}${topedu.value}\'][1]');
                $removeByXpath('/w:document/w:body/w:tbl/w:tr[.=\'个人信息${personal.key1}${personal.value1}${personal.key2}${personal.value2}\'][1]');
                $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[.=\'${expect.key1}${expect.value1}${expect.key2}${expect.value2}\'][1]');
                $removeByXpath('/w:document/w:body/w:tbl/w:tr[.=\'求职意向自我评价：\'][1]');
                if (empty($resume['work'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr[starts-with(.,\'工作经验${work.time}\')][1]');
                $removeByXpath("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl[starts-with(.,'工作经验')]/w:tr[.='工作描述：' or .='主要业绩：' or .='']");
                if (empty($resume['project'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr[starts-with(.,\'项目经验${proj.time}\')][1]');
                $removeByXpath("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl[starts-with(.,'项目经验')]/w:tr[.='所属公司：' or .='项目描述：' or .='责任描述：']");
                if (empty($resume['edu'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr[starts-with(.,\'教育经历${edu.time}\')][1]');
                $removeByXpath("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl[starts-with(.,'教育经历')]/w:tr[.='备注：']");
                if (empty($resume['award'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[starts-with(.,\'▲校内荣誉${award.time}\')][1]');
                if (empty($resume['schooljob'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[starts-with(.,\'▲校内职务${schooljob.time}\')][1]');
                $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[.=\'▲技能/语言${skill.key1}${skill.value1}${skill.key2}${skill.value2}\'][1]');
                if (empty($resume['cert'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[starts-with(.,\'▲证书${cert.time}\')][1]');
                if (empty($resume['train'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[starts-with(.,\'▲培训经历${train.time}\')][1]');
                if (empty($resume['others'])) $removeByXpath('/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[starts-with(.,\'▲其他主题：${others.inforname}\')][1]');

                $removeByXpath("/w:document/w:body/w:tbl/w:tr[w:tc/w:tbl/w:tr[descendant::w:rPr[w:color[@w:val='3876C1'] and w:sz[@w:val=23]]][not(following-sibling::w:tr)]]");

                $border = $dom->createDocumentFragment();
                $border->appendXML('<w:tcBorders xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"></w:tcBorders>');
                $noBottomBorder = $dom->createDocumentFragment();  //删除第一行下边框
                $noBottomBorder->appendXML('<w:bottom xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:val="nil"/>');
                foreach ($xPath->query("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[descendant::w:rPr[w:color[@w:val='3876C1'] and w:sz[@w:val=23]]]/w:tc/w:tcPr[not(w:tcBorders)]") as $tcPr) {
                    $tcPr->appendChild(clone $border);
                }
                foreach ($xPath->query("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[descendant::w:rPr[w:color[@w:val='3876C1'] and w:sz[@w:val=23]]]/w:tc/w:tcPr/w:tcBorders") as $border) {
                    if (count($old = $xPath->query('w:bottom', $border))) {
                        $border->replaceChild(clone $noBottomBorder, $old[0]);
                    } else {
                        $border->appendChild(clone $noBottomBorder);
                    }
                }
                $noTopBorder = $dom->createDocumentFragment();  //删除第二行上边框
                $noTopBorder->appendXML('<w:top xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:val="nil"/>');
                foreach ($xPath->query("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[descendant::w:rPr[w:color[@w:val='3876C1'] and w:sz[@w:val=23]]]/following-sibling::w:tr[1]/w:tc/w:tcPr[not(w:tcBorders)]") as $tcPr) {
                    $tcPr->appendChild(clone $border);
                }
                foreach ($xPath->query("/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[descendant::w:rPr[w:color[@w:val='3876C1'] and w:sz[@w:val=23]]]/following-sibling::w:tr[1]/w:tc/w:tcPr/w:tcBorders") as $border) {
                    if (count($old = $xPath->query('w:top', $border))) {
                        $border->replaceChild(clone $noTopBorder, $old[0]);
                    } else {
                        $border->appendChild(clone $noTopBorder);
                    }
                }
                $removeByXpath('/w:document/w:body/w:tbl/w:tr[.=\'\']');
                $infoNodes = [];
                if (count($infoNode = $xPath->query("/w:document/w:body/w:tbl/w:tr/w:tc/w:p[w:r/w:drawing/wp:inline/wp:docPr[@descr='phone']][1]")))
                    $infoNodes[] = $infoNode[0];
                if (count($infoNode = $xPath->query("/w:document/w:body/w:tbl/w:tr/w:tc/w:p[w:r/w:drawing/wp:inline/wp:docPr[@descr='gender']][1]")))
                    $infoNodes[] = $infoNode[0];
                foreach ($infoNodes as $infoNode) {
                    foreach ($xPath->query("w:r", $infoNode) as $node) {
                        if ($node->textContent == '${undefined}') {
                            $relatedNode = $node->previousSibling;
                            while ($relatedNode) {
                                if ($relatedNode->tagName != 'w:r') {
                                    $relatedNode = $relatedNode->previousSibling;
                                    continue;
                                }
                                $text = trim($relatedNode->textContent);
                                if (!empty($text) && $text != '|') break;
                                $prev = $relatedNode->previousSibling;
                                $infoNode->removeChild($relatedNode);
                                $relatedNode = $prev;
                            }
                            $infoNode->removeChild($node);
                        };
                    }
                    foreach ($xPath->query("w:r", $infoNode) as $node) {
                        $text = trim($node->textContent);
                        if (!empty($text) && $text != '|') break;
                        if (count($xPath->query("w:drawing", $node))) break;
                        $infoNode->removeChild($node);
                    }
                }
                //file_put_contents($this->tmpDir . 'test.xml', $dom->saveXML());
            });
        }
    }

    protected function generateKeyValuePairs(array $dict, $column = 1) {
        $result = [];
        $currentColumn = $column;
        $currentRow = -1;
        foreach ($dict as $key => $value) {
            if ($column > 1) {
                $currentColumn++;
                if ($currentColumn > $column) {
                    $currentRow++;
                    $currentColumn = 1;
                    $result[$currentRow] = ['key1' => $key, 'value1' => $value];
                } else {
                    $result[$currentRow] += ['key' . $currentColumn => $key, 'value' . $currentColumn => $value];
                }
            } else {
                $result[] = ['key' => $key, 'value' => $value];
            }
        }
        if ($column > 1) {
            for ($currentColumn++; $currentColumn <= $column; $currentColumn++) {    //将最后一行（列数不足）用空内容补全
                $result[$currentRow] += ['key' . $currentColumn => '', 'value' . $currentColumn => ''];
            }
        }
        return $result;
    }

    /**
     * 获取简历头像存储的路径
     * @param $resumeInfo
     * @return array
     */
    private function getAvatar($resumeInfo) {
        static $base64ImgRegex = '/^\s*data:image\/(?<fileType>jpeg|jpg|png);base64,\s*(?=[A-Za-z0-9+\/])(?!\s)/';

        try {
            if (isset($resumeInfo['platform'])) {
                if ($resumeInfo['platform'] == '51job') {
                    if (!empty($resumeInfo['resumeid']) && !empty($resumeInfo['accountid']) && $resumeInfo['resumeid'] < 2400000000) {
                        $avatarData = $this->vApi->getResumeAvatar($resumeInfo['accountid'], $resumeInfo['resumeid']);
                        if (preg_match($base64ImgRegex, $avatarData, $matches)) {
                            $rawData = @base64_decode(preg_replace($base64ImgRegex, '', $avatarData, 1));
                            if (!empty($rawData)) {
                                $path = $this->tmpDir . 'avatar_' . $resumeInfo['resumeid'] . '_' . microtime() . '.' . $matches['fileType'];
                                file_put_contents($path, $rawData);
                                return ['path' => $path, 'delFile' => true];
                            }
                        }
                    }
                } elseif ($resumeInfo['platform'] == 'rpo') {
                    if (!empty($resumeInfo['headpath'])) {
                        return ['path' => FILESERVER . '/' . $resumeInfo['headpath'], 'delFile' => false];
                    }
                }
            }
        } catch (\Exception | GuzzleException $e) {
            Log::error(e_dump($e), ['platform' => $resumeInfo['platform'] ?? '', 'resumeId' => $resumeInfo['resumeid'] ?? '', 'accountId' => $resumeInfo['accountid'] ?? '']);
        }

        //请求失败或者没有头像时使用默认头像
        return [
            'path' => !empty($resumeInfo['gender_code']) && $resumeInfo['gender_code'] == 1 ? ROOT_PATH . '/app/Command/ExportFile/resource/woman.png' : ROOT_PATH . '/app/Command/ExportFile/resource/man.png',
            'delFile' => false
        ];
    }

    public function openTask($id) {
        $sql = "update ResumeExport set status='05', processedcount=0, startdate=now(), enddate=null where id=? and status='01'";
        return Db::update($sql, [$id]);
    }

    protected function updateProgress($id, $resumeCount) {
        $sql = "update ResumeExport set processedcount=? where id=?";
        return Db::update($sql, [$resumeCount, $id]);
    }

    public function finishTask($id, $resumeCount, $attachName = null, $filePath = null) {
        if (empty($attachName) || empty($filePath)) $resumeCount = 0;
        $status = $resumeCount > 0 ? '07' : '08';
        if ($status == '08') {
            $attachName = '';
            $filePath = '';
        }
        $sql = 'update ResumeExport set attachname=?, filepath=?, status=?, processedcount=?, enddate=now() where id=?';
        $result = Db::update($sql, [$attachName, $filePath, $status, $resumeCount, $id]);
        $this->pos['id'] = $id;
        file_put_contents($this->configPath, json_encode($this->pos));
        return $result;
    }

    protected function &formatTimespanData(&$data) {
        foreach ($data as &$item) {
            if (!isset($item['time'])) $item['time'] = '';
            if (!is_string($item['time'])) {
                $item['time'] = is_array($item['time']) && !empty($item['time']['full']) ? $item['time']['full'] : '';
            }
        }
        return $data;
    }

    protected function array_flip_xy($source) {
        $result = array();
        foreach ($source as $x => $row) {
            foreach ($row as $y => $val) {
                @$result[$y][$x] = $val;
            }
        }
        return $result;
    }

    protected function formatOutput(&$resumeInfo) {
        array_walk_recursive($resumeInfo, function (&$item) {
            if (is_string($item)) {
                //简历导出时，过滤掉文本内容中的控制字符（不包括换行符）ASCII：0-31（排除10和13回车换行符），以及127
                $item = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]+/u', '', $item);
                $item = htmlspecialchars($item);
                $item = preg_replace('/\r\n|[\r\n]/u', '<w:br/>', $item);
                $item = trim(preg_replace('/[^\S\r\n]*\|[^\S\r\n]*/u', ' | ', $item));
            }
        });
    }

    public function isEmptyDate($date) {
        if (empty($date)) return true;
        if ($date == '0000-00-00 00:00:00') return true;    //0000-00-00
        $timestamp = @strtotime($date);
        if (empty($timestamp) || abs($timestamp) < 86400) return true;   //1970-01-01
        if (date('Y-m-d', $timestamp) == '-0001-11-30') return true;    //0000-00-00
        return false;
    }


    //调试用，打印指定DOMNode（或DOMNode集合）相关信息
    //autoFormat 1 - 普通；2 - 智能去除换行，3 - 所有换行替换为空格，4 - 去除全部换行，否则不处理
    protected function renderNode($node, $autoFormat = -1, $renderAttr = array('class', 'style', 'w:val', 'w:eastasia', 'w:cs')) {
        if ($this->is_iterable($node)) {
            $resultCount = 0;
            foreach ($node as $child) {
                if (!$child instanceof \DOMNode || (!isset($child->tagName) && empty(trim($child->textContent)))) continue;
                $resultCount++;
                $this->renderNode($child, $autoFormat);
            }
            return $resultCount;
        }
        if (!$node instanceof \DOMNode) {
            var_dump('Not a DOMNode: ', $node);
            return;
        }
        $xPath = $node->getNodePath();
        var_dump($xPath);
        if (($xPathSimple = preg_replace('/\[\d+\]/', '', $xPath)) != $xPath) var_dump($xPathSimple);
        if (isset($node->attributes)) {
            foreach ($renderAttr as $attrName) {
                $attr = $node->attributes->getNamedItem($attrName);
                if ($attr && !is_null($attrValue = $attr->value)) {
                    var_dump($attrName . ":\t" . $attrValue);
                }
            }
        }
        $content = $node->textContent;
        if (!empty($content)) var_dump($content);
        echo "-------------------------------------------------------------------------------------------------------------------------------------\r\n";
    }

    protected function is_iterable($target) {
        return is_array($target) || (is_object($target) && ($target instanceof \Traversable));
    }
}